//! Command definition and building functionality

use crate::error::{Result, ShellError};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;

/// Represents a shell command with its arguments and execution context
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Command {
    /// The command program to execute
    pub program: String,
    /// Command arguments
    pub args: Vec<String>,
    /// Working directory for command execution
    pub working_dir: Option<PathBuf>,
    /// Environment variables
    pub env: HashMap<String, String>,
    /// Timeout for command execution
    pub timeout: Option<Duration>,
    /// Whether to capture stdout
    pub capture_stdout: bool,
    /// Whether to capture stderr
    pub capture_stderr: bool,
    /// Whether to inherit parent's environment
    pub inherit_env: bool,
}

impl Command {
    /// Create a new command with the given program
    pub fn new<S: Into<String>>(program: S) -> Self {
        Self {
            program: program.into(),
            args: Vec::new(),
            working_dir: None,
            env: HashMap::new(),
            timeout: None,
            capture_stdout: true,
            capture_stderr: true,
            inherit_env: true,
        }
    }
    
    /// Add an argument to the command
    pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
        self.args.push(arg.into());
        self
    }
    
    /// Add multiple arguments to the command
    pub fn args<I, S>(mut self, args: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.args.extend(args.into_iter().map(|s| s.into()));
        self
    }
    
    /// Set the working directory for the command
    pub fn current_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
        self.working_dir = Some(dir.into());
        self
    }
    
    /// Set an environment variable
    pub fn env<K, V>(mut self, key: K, val: V) -> Self
    where
        K: Into<String>,
        V: Into<String>,
    {
        self.env.insert(key.into(), val.into());
        self
    }
    
    /// Set multiple environment variables
    pub fn envs<I, K, V>(mut self, vars: I) -> Self
    where
        I: IntoIterator<Item = (K, V)>,
        K: Into<String>,
        V: Into<String>,
    {
        self.env.extend(vars.into_iter().map(|(k, v)| (k.into(), v.into())));
        self
    }
    
    /// Set timeout for command execution
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }
    
    /// Set whether to capture stdout
    pub fn capture_stdout(mut self, capture: bool) -> Self {
        self.capture_stdout = capture;
        self
    }
    
    /// Set whether to capture stderr
    pub fn capture_stderr(mut self, capture: bool) -> Self {
        self.capture_stderr = capture;
        self
    }
    
    /// Set whether to inherit parent's environment
    pub fn inherit_env(mut self, inherit: bool) -> Self {
        self.inherit_env = inherit;
        self
    }
    
    /// Validate the command before execution
    pub fn validate(&self) -> Result<()> {
        if self.program.is_empty() {
            return Err(ShellError::invalid_command("Program name cannot be empty"));
        }
        
        if let Some(ref dir) = self.working_dir {
            if !dir.exists() {
                return Err(ShellError::invalid_command(
                    format!("Working directory does not exist: {}", dir.display())
                ));
            }
        }
        
        Ok(())
    }
    
    /// Get the full command line as a string (for display purposes)
    pub fn command_line(&self) -> String {
        let mut parts = vec![self.program.clone()];
        parts.extend(self.args.iter().cloned());
        parts.join(" ")
    }
}

impl Default for Command {
    fn default() -> Self {
        Self::new("")
    }
}

/// Builder pattern for creating commands with a fluent interface
pub struct CommandBuilder {
    command: Command,
}

impl CommandBuilder {
    /// Create a new command builder
    pub fn new<S: Into<String>>(program: S) -> Self {
        Self {
            command: Command::new(program),
        }
    }
    
    /// Add an argument
    pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
        self.command = self.command.arg(arg);
        self
    }
    
    /// Add multiple arguments
    pub fn args<I, S>(mut self, args: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.command = self.command.args(args);
        self
    }
    
    /// Set working directory
    pub fn current_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
        self.command = self.command.current_dir(dir);
        self
    }
    
    /// Set environment variable
    pub fn env<K, V>(mut self, key: K, val: V) -> Self
    where
        K: Into<String>,
        V: Into<String>,
    {
        self.command = self.command.env(key, val);
        self
    }
    
    /// Set timeout
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.command = self.command.timeout(timeout);
        self
    }
    
    /// Build the command
    pub fn build(self) -> Result<Command> {
        self.command.validate()?;
        Ok(self.command)
    }
}